package com.google.code.jtasks.bugzilla.proxy.bug;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;

import com.google.code.jtasks.bugzilla.proxy.ProcessingUtils;

/**
 * This class provides a proxy for the <code>Bug.search</code> XML-RPC method.
 */
public final class BugSearchProxy
{
	private final XmlRpcClient rpcClient;

	/**
	 * Constructs a new {@link BugSearchProxy} instance.
	 * 
	 * @param rpcClient
	 *            the {@link XmlRpcClient} that should be used
	 */
	public BugSearchProxy(XmlRpcClient rpcClient)
	{
		this.rpcClient = rpcClient;
	}

	/**
	 * This method is a proxy for the <code>Bug.search</code> XML-RPC method.
	 * 
	 * @param params
	 *            a set of {@link FieldIncludeFilters} that will be used to filter the bugs returned
	 * @return a {@link SearchResult} object containing the results of the search
	 */
	public SearchResult search(FieldIncludeFilters params)
	{
		Object rpcResultStruct;
		try
		{
			rpcResultStruct = rpcClient.execute("Bug.search", new Object[] { params.convertToRpcStruct() });
		}
		catch (XmlRpcException e)
		{
			// TODO throw a better exception
			throw new RuntimeException(e);
		}

		return new SearchResult(rpcResultStruct);
	}

	/**
	 * Represents the parameters that can be passed to the <code>Bug.search</code> XML-RPC method.
	 */
	public static final class FieldIncludeFilters
	{
		private final Set<Integer> ids;

		/**
		 * Constructs a new {@link FieldIncludeFilters} instance.
		 */
		public FieldIncludeFilters()
		{
			this.ids = new HashSet<Integer>();
		}

		/**
		 * Filters the search results to bugs with the specified ID.
		 * 
		 * @param ids
		 *            the IDs of the bugs to include in the search results
		 */
		public void filterByIds(Collection<Integer> ids)
		{
			this.ids.clear();
			this.ids.addAll(ids);
		}

		/**
		 * Converts this {@link FieldIncludeFilters} instance to a "raw" struct that can be passed to an
		 * {@link XmlRpcClient}.
		 * 
		 * @return a "raw" struct that can be passed to an {@link XmlRpcClient}
		 */
		Map<String, Object> convertToRpcStruct()
		{
			Map<String, Object> rpcStruct = new HashMap<String, Object>();

			rpcStruct.put("id", ProcessingUtils.toIntegerArray(ids));

			return rpcStruct;
		}
	}

	/**
	 * Represents the result returned by the <code>Bug.search</code> XML-RPC method.
	 */
	public static final class SearchResult
	{
		private final List<Bug> bugs;

		/**
		 * Constructs a new {@link SearchResult} instance from the specified
		 * {@link XmlRpcClient#execute(String, Object[])} result object.
		 * 
		 * @param rpcResultStruct
		 *            the {@link XmlRpcClient#execute(String, Object[])} result object to build the
		 *            {@link SearchResult} from
		 */
		public SearchResult(Object rpcResultStruct)
		{
			if (!(rpcResultStruct instanceof Map<?, ?>))
				throw new IllegalArgumentException("unexpected result, wrong root type: " + rpcResultStruct);

			Map<?, ?> rootMap = (Map<?, ?>) rpcResultStruct;
			if (!rootMap.containsKey("bugs"))
				throw new IllegalArgumentException("unexpected result, missing bugs element: "
						+ rpcResultStruct);

			Object bugs = rootMap.get("bugs");
			if (!(bugs instanceof Object[]))
				throw new IllegalArgumentException("unexpected result, wrong bugs type: " + bugs);
			Object[] bugsArray = (Object[]) bugs;

			// Walk through each bug in the array, converting it to a Bug instance
			this.bugs = new ArrayList<Bug>();
			for (Object bug : bugsArray)
			{
				Bug bugModel = new Bug(bug);
				this.bugs.add(bugModel);
			}
		}

		/**
		 * Returns the {@link Bug}s that were found in the XML-RPC result object.
		 * 
		 * @return the {@link Bug}s that were found in the XML-RPC result object
		 */
		public List<Bug> getBugs()
		{
			return bugs;
		}
	}

	/**
	 * Represents a Bugzilla bug, as returned by the <code>Bug.search</code> XML-RPC method.
	 */
	public static final class Bug implements Serializable
	{
		private static final long serialVersionUID = 2829671268823880062L;

		private String alias;
		private String assigned_to;
		private Integer[] blocks;
		private String[] cc;
		private String classification;
		private String component;
		private final Date creation_time;
		private String creator;
		private String deadline;
		private Integer[] depends_on;
		private Integer dupe_of;
		private Double estimated_time;
		private String[] groups;
		private final Integer id;
		private Boolean is_cc_accessible;
		private Boolean is_confirmed;
		private Boolean is_open;
		private Boolean is_creator_accessible;
		private String[] keywords;
		private Date last_change_time;
		private String op_sys;
		private String platform;
		private String priority;
		private String product;
		private String qa_contact;
		private Double remaining_time;
		private String resolution;
		private String[] see_also;
		private String severity;
		private String status;
		private String summary;
		private String target_milestone;
		private String update_token;
		private String url;
		private String version;
		private String whiteboard;

		/**
		 * Constructs a new {@link SearchResult} instance from the specified
		 * {@link XmlRpcClient#execute(String, Object[])} result inner object.
		 * 
		 * @param bugStruct
		 *            the {@link XmlRpcClient#execute(String, Object[])} result inner object to build the
		 *            {@link Bug} from
		 */
		public Bug(Object bugStruct)
		{
			Map<?, ?> fieldMap = ProcessingUtils.castToMap(bugStruct);

			this.alias = ProcessingUtils.extractStringField(fieldMap, "alias", false);
			this.assigned_to = ProcessingUtils.extractStringField(fieldMap, "assigned_to", false);
			this.blocks = ProcessingUtils.extractIntegerArrayField(fieldMap, "blocks");
			this.cc = ProcessingUtils.extractStringArrayField(fieldMap, "cc");
			this.classification = ProcessingUtils.extractStringField(fieldMap, "classification", false);
			this.component = ProcessingUtils.extractStringField(fieldMap, "component", false);
			this.creation_time = ProcessingUtils.extractDateField(fieldMap, "creation_time");
			this.creator = ProcessingUtils.extractStringField(fieldMap, "creator", false);
			this.deadline = ProcessingUtils.extractStringField(fieldMap, "deadline", false);
			this.depends_on = ProcessingUtils.extractIntegerArrayField(fieldMap, "depends_on");
			this.dupe_of = ProcessingUtils.extractIntegerField(fieldMap, "dupe_of", true);
			this.estimated_time = ProcessingUtils.extractDoubleField(fieldMap, "estimated_time");
			this.groups = ProcessingUtils.extractStringArrayField(fieldMap, "groups");
			this.id = ProcessingUtils.extractIntegerField(fieldMap, "id", false);
			this.is_cc_accessible = ProcessingUtils.extractBooleanField(fieldMap, "is_cc_accessible", false);
			this.is_confirmed = ProcessingUtils.extractBooleanField(fieldMap, "is_confirmed", false);
			this.is_open = ProcessingUtils.extractBooleanField(fieldMap, "is_open", false);
			this.is_creator_accessible = ProcessingUtils.extractBooleanField(fieldMap,
					"is_creator_accessible", false);
			this.keywords = ProcessingUtils.extractStringArrayField(fieldMap, "keywords");
			this.last_change_time = ProcessingUtils.extractDateField(fieldMap, "last_change_time");
			this.op_sys = ProcessingUtils.extractStringField(fieldMap, "op_sys", false);
			this.platform = ProcessingUtils.extractStringField(fieldMap, "platform", false);
			this.priority = ProcessingUtils.extractStringField(fieldMap, "priority", false);
			this.product = ProcessingUtils.extractStringField(fieldMap, "product", false);
			this.qa_contact = ProcessingUtils.extractStringField(fieldMap, "qa_contact", false);
			this.remaining_time = ProcessingUtils.extractDoubleField(fieldMap, "remaining_time");
			this.resolution = ProcessingUtils.extractStringField(fieldMap, "resolution", false);
			this.see_also = ProcessingUtils.extractStringArrayField(fieldMap, "see_also");
			this.severity = ProcessingUtils.extractStringField(fieldMap, "severity", false);
			this.status = ProcessingUtils.extractStringField(fieldMap, "status", false);
			this.summary = ProcessingUtils.extractStringField(fieldMap, "summary", false);
			this.target_milestone = ProcessingUtils.extractStringField(fieldMap, "target_milestone", false);
			this.update_token = ProcessingUtils.extractStringField(fieldMap, "update_token", false);
			this.url = ProcessingUtils.extractStringField(fieldMap, "url", false);
			this.version = ProcessingUtils.extractStringField(fieldMap, "version", false);
			this.whiteboard = ProcessingUtils.extractStringField(fieldMap, "whiteboard", false);

			// TODO custom fields
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#alias
		 */
		public String getAlias()
		{
			return alias;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#assigned_to
		 */
		public String getAssigned_to()
		{
			return assigned_to;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#blocks
		 */
		public Integer[] getBlocks()
		{
			return blocks;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#cc
		 */
		public String[] getCc()
		{
			return cc;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#classification
		 */
		public String getClassification()
		{
			return classification;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#component
		 */
		public String getComponent()
		{
			return component;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#creation_time
		 */
		public Date getCreation_time()
		{
			return creation_time;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#creator
		 */
		public String getCreator()
		{
			return creator;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#deadline
		 */
		public String getDeadline()
		{
			return deadline;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#depends_on
		 */
		public Integer[] getDepends_on()
		{
			return depends_on;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#dupe_of
		 */
		public Integer getDupe_of()
		{
			return dupe_of;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#estimated_time
		 */
		public Double getEstimated_time()
		{
			return estimated_time;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#groups
		 */
		public String[] getGroups()
		{
			return groups;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#id
		 */
		public Integer getId()
		{
			return id;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#is_cc_accessible
		 */
		public Boolean getIs_cc_accessible()
		{
			return is_cc_accessible;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#is_confirmed
		 */
		public Boolean getIs_confirmed()
		{
			return is_confirmed;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#is_open
		 */
		public Boolean getIs_open()
		{
			return is_open;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http
		 *      ://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#is_creator_accessible
		 */
		public Boolean getIs_creator_accessible()
		{
			return is_creator_accessible;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#keywords
		 */
		public String[] getKeywords()
		{
			return keywords;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#last_change_time
		 */
		public Date getLast_change_time()
		{
			return last_change_time;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#op_sys
		 */
		public String getOp_sys()
		{
			return op_sys;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#platform
		 */
		public String getPlatform()
		{
			return platform;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#priority
		 */
		public String getPriority()
		{
			return priority;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#product
		 */
		public String getProduct()
		{
			return product;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#qa_contact
		 */
		public String getQa_contact()
		{
			return qa_contact;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#remaining_time
		 */
		public Double getRemaining_time()
		{
			return remaining_time;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#resolution
		 */
		public String getResolution()
		{
			return resolution;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#see_also
		 */
		public String[] getSee_also()
		{
			return see_also;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#severity
		 */
		public String getSeverity()
		{
			return severity;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#status
		 */
		public String getStatus()
		{
			return status;
		}

		/**
		 * Returns the title/summary for this bug: a short {@link String} that summarizes the issue or task.
		 * 
		 * @return the title/summary for this bug: a short {@link String} that summarizes the issue or task
		 */
		public String getSummary()
		{
			return summary;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#target_milestone
		 */
		public String getTarget_milestone()
		{
			return target_milestone;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#update_token
		 */
		public String getUpdate_token()
		{
			return update_token;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#url
		 */
		public String getUrl()
		{
			return url;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#version
		 */
		public String getVersion()
		{
			return version;
		}

		/**
		 * Returns the value of this field, as returned by the RPC call.
		 * 
		 * @return the value of this field, as returned by the RPC call
		 * @see http://www.bugzilla.org/docs/4.0/en/html/api/Bugzilla/WebService/Bug.html#whiteboard
		 */
		public String getWhiteboard()
		{
			return whiteboard;
		}
	}
}
